﻿using SBSSHCommon;
using SBSSHKeyStorage;
using SBSSHServer;
using System;
using System.Net.Sockets;
using System.Threading;

namespace WindowsFormsApplication1
{
    public class SSHSession
    {
        private MSLogger _logger;

        private string _username;

        private TElSSHServer _sshServer;
        private Socket _socket;
        private System.Timers.Timer _socketSessionTimer;
        private object _lock = new object();
        private object _synch;
        private Thread _thread;

        public delegate void SessionClosedHandler(SSHSession sender);
        public event SessionClosedHandler SessionClosed;

        public SSHSession(Socket socket, int id, MSLogger logger)
        {
            _logger = logger;
            _sshServer = new TElSSHServer();
            _socket = socket;

            try
            {
                _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, new LingerOption(true, 300));
            }
            catch (Exception ex)
            {
                _logger.Debug(String.Format("ERROR - Setting socket options.\r\n\r\n{0}", ex.GetFullException()));
            }

            _socketSessionTimer = new System.Timers.Timer();
            _socketSessionTimer.Elapsed += new System.Timers.ElapsedEventHandler(onSocketSessionTimeout);
            _socketSessionTimer.AutoReset = true;

            _synch = new object();

            SetupServer();

            _thread = new Thread(new ThreadStart(readThread));
            _thread.Start();
        }

        private void onSocketSessionTimeout(object sender, System.Timers.ElapsedEventArgs e)
        {
            _socketSessionTimer.Stop();
            CloseSession();
        }

        public void Start()
        {
            restartSocketSessionTimer();
        }

        public void Dispose()
        {
            CloseSession();
        }

        public void CloseSession()
        {
            if (_sshServer.Active)
            {
                Monitor.Enter(_lock);
                try
                {
                    _sshServer.Close(true);
                }
                finally
                {
                    Monitor.Exit(_lock);
                }
            }

        }

        private void doSocketShutdown()
        {
            try
            {
                if (_socket != null)
                {
                    _logger.Debug("Closing Socket");
                    _socket.Shutdown(SocketShutdown.Both);
                    _socket.Close();
                    _socket = null;
                }
            }
            catch (Exception ex)
            {
                _logger.Debug(String.Format("ERROR - Shutting down socket.\r\n\r\n{0}", ex.GetFullException()));
            }
        }

        private void SetupServer()
        {
            var hostKeys = new TElSSHMemoryKeyStorage();

            try
            {
                var key = new TElSSHKey();
                var serverKey = SBUtils.Unit.BytesOfString("-----BEGIN RSA PRIVATE KEY-----\r\nMIICXQIBAAKBgQClxXYBBwT7mrlynUZUqVlHTKTpI5wbmVzhP4bfWafSU9In35kL\r\naYESlRMrri+zulahUv/5k336IcvsPQvcPZJrl3j4SVYjGnjvsILIggoaz0Nkzmtc\r\nQfFdG4cqwAS2M6SEXe+wX4o+r5E6t35aRPvaNIPrtq8a4Yc8BiTE31K1NwIEAAEA\r\nAQKBgQCRuwvIT3Fz90t8lQLVPqN76Mc5JdMA+EwMwRpCk8DTSFkCMLGMtM2Blohp\r\n6BGLgQ7Td/IMaDthgACTx/77OS3T+eelnMYaKnFxWd6vCsOJicp4009sjSyYhesv\r\nrwrn0P3NQaqZHWgCECbcxgeD4mQz6zCtI8EnbzqgCYa/mp+t0QJBAMnIzEBYkXN8\r\n9kDIt4n2iNRQawtmVy8ijjHi1t3C5Q+HnhjQUlgw/C9KDDcVyeo5xXzSGhR6M+AL\r\nPYBmzM0P+1MCQQDST5ohhOaZHSP68jesV1bOeJNzKZAKnjnRVoawL8gvBwl7Saxt\r\naqmPTSiGl6MkOEpzDQWcNPEHQGcwjOZ/BQYNAkAKuIpxmMMRmcATrRZjVB/s9+DY\r\nGDXloTZg3CaCXcqByrjRpK/bU67ybEK94oB521vsx9Tu5Zc//hjka5HSC1lVAkAy\r\nj8iyfk0iSos37bvMx0KraPmqGjfzFY848OB4w0QRalOxPEkkFRymwxoXIaWrM41S\r\nk7LhlyjLYYRte7J+nyqRAkAIEtULrfkI5SYQCh8/lsoPWK4mqbSTaRE+olmN/7G+\r\nuCFft8LM1Hz53oOQcpI6Y94EwQx6uJWIs8trxTdNqiMS\r\n-----END RSA PRIVATE KEY-----\r\n");
                int result = key.LoadPrivateKey(serverKey, serverKey.Length, "");

                if (result == 0)
                {
                    hostKeys.Add(key);

                    _logger.Debug("Host private key successfully loaded");
                }
                else
                {
                    _logger.Debug(String.Format("ERROR - Host private key could not be loaded. Error code: {0}", result));
                }
            }
            catch (Exception ex)
            {
                _logger.Debug(String.Format("ERROR - Setting the host private key.\r\n\r\n{0}", ex.GetFullException()));
            }

            _sshServer.KeyStorage = hostKeys;
            _sshServer.AllowedSubsystems.Add("sftp");
            _sshServer.OnAuthAttempt += new TSSHAuthAttemptEvent(onAuthAttempt);
            _sshServer.OnAuthPassword += new TSSHAuthPasswordEvent(onAuthPassword);
            _sshServer.OnAuthFailed += new TSSHAuthenticationFailedEvent(onAuthFailed);
            _sshServer.OnSend += new SBSSHCommon.TSSHSendEvent(onSend);
            _sshServer.OnReceive += new TSSHReceiveEvent(onReceive);
            _sshServer.OnOpenSubsystem += new TSSHOpenSubsystemEvent(onOpenSubsystem);
        }

        private void onAuthAttempt(object sender, string username, int authType, ref bool accept)
        {
            _logger.Debug(String.Format("SSH Server OnAuthAttempt event triggered. Authentication type: {0}", authType));
            if (authType == 4) {
                _logger.Debug("SSH Server Password Authentication requested. Accepting connection");
                accept = true;
            }
        }

        private void onAuthFailed(object sender, int authType)
        {
            _logger.Debug("SSH Server Authentication failed");
        }

        private void onAuthPassword(object sender, string username, string password, ref bool accept, ref bool forceChangePassword)
        {
            _logger.Debug("SSH Server Password Authentication event triggered. Accepting connection");

            _username = username;

            //TODO: Implement any password authentication here
            accept = true;
        }

        private void onSend(object Sender, byte[] Buffer)
        {
            try
            {
                int toSend = Buffer.Length;
                int sent;
                int ptr = 0;
                while (toSend > 0)
                {
                    sent = _socket.Send(Buffer, ptr, toSend, SocketFlags.None);
                    ptr += sent;
                    toSend -= sent;
                }
            }
            catch (Exception ex)
            {
                _logger.Debug(String.Format("Socket send operation failed.\r\n\r\n{0}" + ex.GetFullException()));
                if (_sshServer.Active)
                {
                    _sshServer.OnSend -= new SBSSHCommon.TSSHSendEvent(onSend);
                    Monitor.Enter(_lock);
                    try
                    {
                        _sshServer.Close(true);
                    }
                    finally
                    {
                        Monitor.Exit(_lock);
                    }
                }
                else CloseSession();
            }
        }

        private void onReceive(object sender, ref byte[] buffer, int maxSize, out int written)
        {
            try
            {
                if (_socket.Poll(100000, SelectMode.SelectRead))
                {
                    written = _socket.Receive(buffer, maxSize, SocketFlags.None);
                    if (written == 0)
                    {
                        //_logger.Debug("No data received... attempting to close connection");
                        if (_sshServer.Active)
                        {
                            _sshServer.OnSend -= new SBSSHCommon.TSSHSendEvent(onSend);
                            Monitor.Enter(_lock);
                            try
                            {
                                _sshServer.Close(true);
                            }
                            finally
                            {
                                Monitor.Exit(_lock);
                            }
                        }
                        else
                        {
                            CloseSession();
                        }
                    }
                    else
                    {
                        //_logger.Debug("Data received... resetting session timeout timer");

                        restartSocketSessionTimer();
                    }
                }
                else
                {
                    _logger.Debug("Socket polling failed");

                    written = 0;
                }
            }
            catch (Exception ex)
            {
                written = 0;

                _logger.Debug(String.Format("ERROR - Receiving data.\r\n\r\n{0}", ex.GetFullException()));

                if (_sshServer.Active)
                {
                    Monitor.Enter(_lock);
                    try
                    {
                        _sshServer.Close(true);
                    }
                    finally
                    {
                        Monitor.Exit(_lock);
                    }
                }
                else
                {
                    CloseSession();
                }
            }
        }
        private void restartSocketSessionTimer()
        {
            _socketSessionTimer.Enabled = false;
            _socketSessionTimer.Interval = 5 * 60 * 1000;
            _socketSessionTimer.Enabled = true;
        }
        private void onOpenSubsystem(object sender, TElSSHTunnelConnection connection, string subsystem)
        {
            if (subsystem == "sftp")
            {
                _logger.Debug("SFTP subsystem requested");
                var sftp = new SFTPSession(connection, _logger, _username);
            }
        }

        private void readThread()
        {
            _logger.Debug("SSH reading thread fired");

            try
            {
                Monitor.Enter(_lock);
                try
                {
                    _sshServer.Open();
                }
                finally
                {
                    Monitor.Exit(_lock);
                }

                while (_socket != null && _socket.Connected)
                {
                    //_logger.Debug("Socket available & connected in reading thread");

                    if (_socket.Poll(1000000, SelectMode.SelectRead))
                    {
                        Monitor.Enter(_lock);
                        try
                        {
                            //_logger.Debug("Data available in SSH reading thread");

                            _sshServer.DataAvailable();
                        }
                        finally
                        {
                            Monitor.Exit(_lock);
                        }

                        Thread.Sleep(0);
                    }
                    else
                    {
                        Thread.Sleep(50);
                    }
                }
            }
            catch (Exception ex)
            {
                if (!(ex is ThreadAbortException))
                {
                    _logger.Debug(String.Format("ERROR - Reading thread.\r\n\r\n{0}", ex.GetFullException()));
                }
                else
                {
                    if (this.SessionClosed != null)
                    {
                        this.SessionClosed(this);
                        _thread = null;
                    }
                }
            }
        }
    }
}
